package cn.xzqwjw.taskmanager.utils;

import cn.xzqwjw.taskmanager.common.customEnum.ResponseCodeEnum;
import cn.xzqwjw.taskmanager.common.exception.CustomException;
import cn.xzqwjw.taskmanager.domain.pojo.SysAdmin;
import cn.xzqwjw.taskmanager.domain.pojo.SysPurview;
import cn.xzqwjw.taskmanager.domain.pojo.SysRole;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.logging.log4j.util.Strings;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

/**
 * 用 Jjwt 操作 jwt 的工具
 *
 * @author rush
 */
public class JwtUtil {

  /**
   * 签发人
   */
  private static final String ISS = "rush";

  /**
   * 签发主题
   */
  private static final String SUB = "rush";

  /**
   * 有效时长 3600 秒（1小时）
   */
  private static final long EXPIRATION = 3600L;

  /**
   * 刷新间隔 1800 秒（半小时）
   */
  private static final long REFRESH_TOKEN_INTERVAL = 1800L;

  /**
   * 签名算法
   */
  private static final SignatureAlgorithm ALGORITHM = SignatureAlgorithm.HS512;

  /**
   * 密钥
   */
  private static final String SECRET_KEY = "jWt_seCRet_xzqwjw@!_^&SD_ld93_2024";

  private static final String SEPARATION_CHARACTER = ",";

  //////////////////////////////////////// 生成 Jwt ////////////////////////////////////////

  /**
   * 创建 JWT，用登录成功后得到的SysAdmin重载
   *
   * @param sysAdmin   登录成功后得到的SysAdmin
   * @param expiration 过期时间（秒）
   * @return JWT
   */
  public static String createJwt(SysAdmin sysAdmin, Long expiration) {
    if (Objects.isNull(sysAdmin) ||
        Objects.isNull(sysAdmin.getId()) ||
        Objects.isNull(sysAdmin.getUsername()) ||
        Objects.isNull(sysAdmin.getAuthorities())) {
      return "";
    }
    String id = sysAdmin.getId().toString();
    String username = sysAdmin.getUsername();
    List<SysRole> listRole = sysAdmin.getRoleList();
    List<SysPurview> listPurview = sysAdmin.getPurviewList();
    StringBuilder sbRoles = new StringBuilder();
    StringBuilder sbPurviews = new StringBuilder();
    for (SysRole r : listRole) {
      sbRoles.append(r.getCode()).append(",");
    }
    for (SysPurview p : listPurview) {
      sbPurviews.append(p.getCode()).append(",");
    }
    String roles = sbRoles.toString();
    String purviews = sbPurviews.toString();
    // 去除最后一位的","，这里要做判断
    if (StringUtils.endsWithIgnoreCase(roles, ",")) {
      roles = roles.substring(0, roles.length() - 1);
    }
    if (StringUtils.endsWithIgnoreCase(purviews, ",")) {
      purviews = purviews.substring(0, purviews.length() - 1);
    }
    return createJwt(id, username, expiration, roles, purviews);
  }

  /**
   * 创建管理员用的 JWT，用默认值重载
   *
   * @param id       令牌ID
   * @param subject  用户名
   * @param roles    角色字符串
   * @param purviews 权限字符串
   * @return JWT
   */
  public static String createJwt(String id, String subject, Long expiration,
                                 String roles, String purviews) {
    return createJwt(id, ISS, subject, expiration, roles, purviews, ALGORITHM, SECRET_KEY);
  }

  /**
   * 创建 JWT，最基本的重载
   *
   * @param id         令牌ID
   * @param iss        签发人
   * @param subject    用户名
   * @param expiration 过期时间
   * @param roles      角色数组
   * @param purviews   权限数组
   * @param algorithm  加密算法
   * @param secretKey  密钥字符串
   * @return JWT
   */
  private static String createJwt(String id,
                                  String iss,
                                  String subject,
                                  Long expiration,
                                  String roles,
                                  String purviews,
                                  SignatureAlgorithm algorithm,
                                  String secretKey) {
    JwtBuilder jwt = Jwts.builder();
    // 重点注意：setClaims 即 自定义的私有声明必须在前，不然会被覆盖
    // 自定义私有声明，包括角色和权限数组，这里的map一定是 Map<String, Object>
    Map<String, Object> map = new HashMap<>(16);
    map.put("roles", roles);
    map.put("purviews", purviews);
    jwt.setClaims(map);
    if (Strings.isNotBlank(id)) {
      jwt.setId(id);
    }
    // 设置签发人
    jwt.setIssuer(Optional.ofNullable(iss).orElse(ISS));
    // 设置用户名
    jwt.setSubject(Optional.ofNullable(subject).orElse(SUB));
    // if (Strings.isNotBlank(subject)) jwt.setSubject(subject);
    // 获取当前时间戳
    long currentTimeMillis = System.currentTimeMillis();
    // 根据当前时间戳设置签发时间
    jwt.setIssuedAt(new Date(currentTimeMillis));
    // 设置过期时间，在现在时间基础上加 expiration * 1000 毫秒
    jwt.setExpiration(new Date(currentTimeMillis + expiration * 1000));
    // 设置加密算法
    if (Objects.isNull(algorithm)) {
      algorithm = ALGORITHM;
    }
    // 设置密钥
    if (!StringUtils.hasLength(secretKey)) {
      secretKey = SECRET_KEY;
    }
    // 根据密匙设置签名算法
    jwt.signWith(algorithm, secretKey);
    // 设置压缩，可选GZIP
    // jwt.compressWith(CompressionCodecs.DEFLATE);
    return jwt.compact();
  }

  /**
   * 根据用户的登录时间生成动态私钥
   *
   * @param instant 用户的登录时间，也就是申请令牌的时间
   * @return 动态私钥字符串
   */
  public static String setSecretKey(Instant instant) {
    return String.valueOf(instant.getEpochSecond());
  }

  // ==================== 解析 Jwt ====================

  /**
   * 从 token 中获取id
   *
   * @param token JWT 字符串
   * @return id
   */
  public static String getId(String token) {
    return getTokenBody(token).getId();
  }

  /**
   * 如果是管理员，从 token 中获取的是登录用户名
   * 如果微信小程序用户，从 token 中获取的是 IdOpen
   *
   * @param token JWT 字符串
   * @return 登录用户名或者 IdOpen
   */
  public static String getUsername(String token) {
    return getTokenBody(token).getSubject();
  }

  /**
   * 从 token 中获取用户角色
   *
   * @param token JWT 字符串
   * @return 用户角色数组
   */
  public static String getRoles(String token) {
    Claims claims = getTokenBody(token);
    if (Objects.isNull(claims)) {
      return "";
    }
    return claims.get("roles", String.class);
  }

  /**
   * 从 token 中获取用户权限
   *
   * @param token JWT 字符串
   * @return 用户权限数组
   */
  public static String getPurviews(String token) {
    Claims claims = getTokenBody(token);
    if (Objects.isNull(claims)) {
      return "";
    }
    return claims.get("purviews", String.class);
  }

  /**
   * 从 token 中获取 JWT 令牌的颁发时间
   *
   * @param token JWT 令牌字符串
   * @return JWT 的颁发时间
   */
  public static Date getIssueAt(String token) {
    return getTokenBody(token).getIssuedAt();
  }

  /**
   * 从 token 中获取 JWT 的过期时间
   *
   * @param token JWT 字符串
   * @return JWT 的过期时间
   */
  public static Date getExpiration(String token) {
    return getTokenBody(token).getExpiration();
  }

  /**
   * 校验是否过期
   *
   * @param token JWT 字符串
   * @return 是否过期的布尔值
   */
  public static boolean isExpired(String token) {
    // Date1.after(Date2),当Date1大于Date2时，返回TRUE，当小于等于时，返回false
    // Date1.before(Date2)，当Date1小于Date2时，返回TRUE，当大于等于时，返回false
    // 若要校验Date2必须比Date1小，且不能相等时，使用 !Date1.after(Date2)
    return getExpiration(token).before(new Date());
  }

  /**
   * 校验 jwt 是否是正确的 jwt
   *
   * @param token JWT 字符串
   * @return 布尔值
   */
  public static boolean isChecked(String token) {
    Claims claims = getTokenBody(token);
    return claims != null;
  }

  /**
   * 获得 token 的 body
   *
   * @param token JWT 字符串
   * @return Claims
   */
  private static Claims getTokenBody(String token) {
    Claims claims;
    try {
      claims = Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
    } catch (Exception e) {
      claims = null;
    }
    return claims;
  }

  /**
   * 根据令牌发布时间判断是否需要刷新令牌
   *
   * @param jwtToken 要检验的 Jwt 令牌字符串
   * @return 布尔值
   */
  public static boolean isNeedRefreshToken(String jwtToken, long refreshTokenInterval) {
    Date issueAt = getIssueAt(jwtToken);
    // 将签发时间根据当地时区转换成 LocalDateTime
    LocalDateTime issueTime = LocalDateTime.ofInstant(issueAt.toInstant(), ZoneId.systemDefault());
    // isAfter() 检查此时间是否在指定时间之后
    // 因此下面的语句得到现在的时间加上 刷新时间间隔 之后是否还在令牌到期时间之后
    // 如果还在令牌到期时间之内，说明不需要刷新，返回false
    // 如果在令牌到期时间之外，说明需要刷新了，返回true
    // 比如签发时间是 20:00:00，过期时长是10分钟，那么过期时间是 20:10:00
    // 设置刷新时间间隔是5分钟，就是说5分钟之内会自动刷新令牌
    // 如果现在时间是20:04:00，那么现在时间的5分钟之后是 20:09:00，
    // 那么返回 false，说明不需要重新生成 jwt
    // 如果现在时间是20:06:00，那么现在时间的5分钟之后是 20:11:00,
    // 那么返回 true， 说明需要重新生成 jwt
    return LocalDateTime.now().minusSeconds(refreshTokenInterval).isAfter(issueTime);
  }

  // ==================== 从request中获取 Jwt ====================

  public static String getIdFromRequestJwt(HttpServletRequest request) {
    String jwtString = request.getHeader("authorization");
    if (!StringUtils.hasText(jwtString)) {
      throw new CustomException(ResponseCodeEnum.BAD_REQUEST);
    }
    if (jwtString.startsWith("Bearer ")) {
      jwtString = jwtString.substring(7);
    } else {
      throw new CustomException(ResponseCodeEnum.JWT_BAD_CHECK);
    }
    if (!isChecked(jwtString)) {
      throw new CustomException(ResponseCodeEnum.JWT_BAD_CHECK);
    }
    if (isExpired(jwtString)) {
      throw new CustomException(ResponseCodeEnum.JWT_EXPIRATION);
    }
    return getId(jwtString);
  }

}
